#include <device/pic.h>
#include <device/cpu.h>
#include <kernel/global.h>
#include <io.h>
#include <debug.h>

#include <graphic.h>
#include <std/stdio.h>

#define IA32_APIC_BASE        0x0000001b
#define IA32_APIC_BASE_BSP    (1 << 8)
#define IA32_APIC_BASE_ENABLE (1 << 11)

apic_map_t apic_map;

PUBLIC bool check_apic()
{
    uint32_t a,b,c,d;
    cpuid(1,0,&a,&b,&c,&d);
    return (d & (1 << 9)) && (c & (1 << 21));
}

PRIVATE void detect_cores()
{
    apic_map.local_apic_address   = 0;
    apic_map.ioapic_address       = 0;
    apic_map.ioapic_index_address = NULL;
    apic_map.ioapic_data_address  = NULL;
    apic_map.ioapic_EOI_address   = NULL;
    apic_map.number_of_cores = 0;

    MADT_t* madt = (MADT_t*)KADDR_P2V(g_boot_info.madt_addr);
    apic_map.local_apic_address = madt->LocalApicAddress;
    uint8_t* p = (uint8_t*)(madt + 1);
    uint8_t* p2 = (uint8_t*)madt + madt->Header.Length;
    for (p = (uint8_t*)(madt + 1);p < p2;p += p[1]/* Record Length */)
    {
        switch(p[0]) // Entry Type
        {
            case 0:
                if (p[4] & 1)
                {
                    apic_map.lapic_id[apic_map.number_of_cores++] = p[3];
                }
                break;
            case 1:
                apic_map.ioapic_address = (uint64_t)*(uint32_t*)(p + 4);
                break;
            case 5:
                apic_map.local_apic_address = *(uint64_t*)(p + 4);
                break;
        }
    }
    apic_map.ioapic_index_address = (uint8_t*)(apic_map.ioapic_address + 0UL);
    apic_map.ioapic_data_address  = (uint32_t*)(apic_map.ioapic_address + 0x10UL);
    apic_map.ioapic_EOI_address   = (uint32_t*)(apic_map.ioapic_address + 0x40UL);
}

PRIVATE void local_apic_init()
{
    uint64_t volatile val;
    val = rdmsr(IA32_APIC_BASE);
    val |= IA32_APIC_BASE_ENABLE;
    wrmsr(IA32_APIC_BASE,val);

    // enable SVR[8],SVR[12]
    val =  *(uint64_t*)(KADDR_P2V(apic_map.local_apic_address) + 0x0f0);
    io_mfence();
    val = val | (1 << 12) | (1 << 8);
    *(uint64_t*)(KADDR_P2V(apic_map.local_apic_address) + 0x0f0) = val;
    io_mfence();

    *(uint64_t*)(KADDR_P2V(apic_map.local_apic_address) + 0x2f0) = 0x10000;
    io_mfence();
    *(uint64_t*)(KADDR_P2V(apic_map.local_apic_address) + 0x320) = 0x10000;
    io_mfence();
    *(uint64_t*)(KADDR_P2V(apic_map.local_apic_address) + 0x330) = 0x10000;
    io_mfence();
    *(uint64_t*)(KADDR_P2V(apic_map.local_apic_address) + 0x340) = 0x10000;
    io_mfence();
    *(uint64_t*)(KADDR_P2V(apic_map.local_apic_address) + 0x350) = 0x10000;
    io_mfence();
    *(uint64_t*)(KADDR_P2V(apic_map.local_apic_address) + 0x360) = 0x10000;
    io_mfence();
    *(uint64_t*)(KADDR_P2V(apic_map.local_apic_address) + 0x370) = 0x10000;
    io_mfence();
    return;
}


PRIVATE uint64_t ioapic_rte_read(uint8_t index)
{
    uint64_t ret;
    *(uint8_t*)KADDR_P2V(apic_map.ioapic_index_address) = index + 1;
    io_mfence();
    ret = *(uint32_t*)KADDR_P2V(apic_map.ioapic_data_address);
    ret <<= 32;
    io_mfence();

    *(uint8_t*)KADDR_P2V(apic_map.ioapic_index_address) = index;
    io_mfence();
    ret |= *(uint32_t*)KADDR_P2V(apic_map.ioapic_data_address);
    io_mfence();

    return ret;
}

PRIVATE void ioapic_rte_write(uint8_t index,uint64_t value)
{
    *(uint8_t*)KADDR_P2V(apic_map.ioapic_index_address) = index;
    io_mfence();
    *(uint32_t*)KADDR_P2V(apic_map.ioapic_data_address) = value & 0xffffffff;
    value >>= 32;
    io_mfence();

    *(uint8_t*)KADDR_P2V(apic_map.ioapic_index_address) = index + 1;
    io_mfence();
    *(uint32_t*)KADDR_P2V(apic_map.ioapic_data_address) = value & 0xffffffff;
    io_mfence();
}

/**
 * @brief 允许I/O APIC pin引脚的中断
 * @param pin 中断引脚
 * @param vector 中断向量号
*/
PUBLIC void ioapic_enable(uint64_t pin,uint64_t vector)
{
    uint64_t value = 0;
    value = ioapic_rte_read(pin * 2 + 0x10);
    value = value & (~0x100ffUL);
    ioapic_rte_write(pin * 2 + 0x10,value | vector);
}

PRIVATE void ioapic_init()
{
    /* 屏蔽所有中断 */
    int i;
    for (i = 0x10;i < 0x40;i += 2)
    {
        ioapic_rte_write(i,0x10000);
    }
    ioapic_enable(2,0x20);
    return;
}


PUBLIC void init_apic()
{
    detect_cores();
    char s[128];
    position_t p = {10,500};
    sprintf(s,"detect_cores: lapic addr %p,lapic id %p, cores: %d\n",apic_map.local_apic_address,apic_map.lapic_id[0],apic_map.number_of_cores);
    pr_str(&g_boot_info.graph_info,&p,make_color(255,255,255),s,16);

    LOG_INFO("detect_cores: lapic addr %p,lapic id %p, cores: %d\n",apic_map.local_apic_address,apic_map.lapic_id[0],apic_map.number_of_cores);
    // 禁止8259A的所有中断
    io_out8(PIC_M_DATA, 0xff ); /* 11111111 禁止所有中断 */
    io_out8(PIC_S_DATA, 0xff ); /* 11111111 禁止所有中断 */

    local_apic_init();
    ioapic_init();

    return;
}